﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jayden.Dll.Cards.Poker.Enums;
using System.Threading;

namespace Jayden.Dll.Cards.Poker
{
	public abstract class PokerGameConfiguration
	{
		protected int m_NumberOfSeats = 9;
		protected PokerCurrency m_Currency = PokerCurrency.EUR;

		public int NumberOfSeats { get { return m_NumberOfSeats; } }
		public PokerCurrency Currency { get { return m_Currency; } }

		public abstract bool IsTournament { get; }
		public abstract bool IsCashGame { get; }

		public abstract PokerType Type { get; }
		public abstract PokerLimit Limit { get; }
		public abstract ulong Ante { get; }
		public abstract ulong SmallBlind { get; }
		public abstract ulong BigBlind { get; }

		public abstract ulong GetAnte(PokerDealer dealer);
		public abstract ulong GetSmallBlind(PokerDealer dealer);
		public abstract ulong GetBigBlind(PokerDealer dealer);

		public abstract bool Finished { get; }
	}

	public class PokerCashGameConfiguration : PokerGameConfiguration
	{
		private PokerType m_Type = PokerType.TexasHoldem;
		private PokerLimit m_Limit = PokerLimit.NoLimit;
		private ulong m_Ante = 0;
		private ulong m_SmallBlind = 1;
		private ulong m_BigBlind = 2;
		private ulong m_MinimumBuyIn = 100;
		private ulong m_MaximumBuyIn = 400;

		public PokerCashGameConfiguration(int seats, PokerLimit limit, PokerType type, PokerCurrency currency, ulong ante, ulong smallBlind, ulong bigBlind, ulong minBuyIn, ulong maxBuyIn)
		{
			m_NumberOfSeats = seats;
			m_Limit = limit;
			m_Type = type;
			m_Currency = currency;
			m_Ante = ante;
			m_SmallBlind = smallBlind;
			m_BigBlind = bigBlind;
			m_MinimumBuyIn = minBuyIn;
			m_MaximumBuyIn = maxBuyIn;
		}

		public override PokerType Type { get { return m_Type; } }
		public override PokerLimit Limit { get { return m_Limit; } }
		public override ulong Ante { get { return m_Ante; } }
		public override ulong SmallBlind { get { return m_SmallBlind; } }
		public override ulong BigBlind { get { return m_BigBlind; } }

		public ulong MinimumBuyIn { get { return m_MinimumBuyIn; } }
		public ulong MaximumBuyIn { get { return m_MaximumBuyIn; } }

		public override bool IsTournament { get { return false; } }
		public override bool IsCashGame { get { return true; } }

		public override ulong GetAnte(PokerDealer dealer) { return Ante; }
		public override ulong GetSmallBlind(PokerDealer dealer) { return SmallBlind; }
		public override ulong GetBigBlind(PokerDealer dealer) { return BigBlind; }

		public override bool Finished { get { return false; } }
	}

	public class PokerTournamentConfiguration : PokerGameConfiguration
	{
		public class BettingStructureEntry
		{
			public int Level;
			public ulong SmallBlind;
			public ulong BigBlind;
			public ulong Ante;
			public int TimeBank;
			public int Duration;

			public override string ToString()
			{
				return string.Format("[{0}] {1}/{2} {3} {4} {5}", Level, SmallBlind, BigBlind, Ante, Duration, TimeBank);
			}
		}
		public class PayoutEntry
		{
			public int FirstPosition;
			public int LastPosition;
			public ulong Amount;
		}

		private PokerType m_Type = PokerType.TexasHoldem;
		private PokerLimit m_Limit = PokerLimit.NoLimit;
		private bool m_Started = false;
		private bool m_Finished = false;
		private bool m_AllowEntry = true;
		private byte m_RebuyAllowed = 2;
		private byte m_AddOnAllowed = 0;
		private int m_LateRegigstrationDuration = 20;
		private int m_RebuyDuration = 20;
		private PokerCurrency m_EntryCurrency = PokerCurrency.EUR;
		private int m_Level = 0;
		private ulong m_EntryPrice = 200;
		private ulong m_RebuyPrice = 200;
		private ulong m_AddOnPrice = 200;
		private ulong m_EntryRake = 0;
		private ulong m_StartingChips = 1500;
		private ulong m_RebuyChips = 1500;
		private ulong m_AddOnChips = 2000;
		private int m_MaximumEntries = 9;
		private int m_MinimumEntries = 2;
		private DateTime m_StartTime;
		private BettingStructureEntry[] m_BettingStructure;
		private bool m_PayoutLocked = false;
		private PayoutEntry[] m_Payout;
		private string m_TournamentName = "Tournament";

		private List<PokerPlayer> m_Finisheds = new List<PokerPlayer>();
		private HashSet<PokerPlayer> m_Entrants = new HashSet<PokerPlayer>();
		private int m_TotalRebuyCount;
		private int m_TotalAddOnCount;
		private Dictionary<PokerPlayer, byte> m_RebuyCount = new Dictionary<PokerPlayer,byte>();
		private Dictionary<PokerPlayer, byte> m_AddOnCount = new Dictionary<PokerPlayer,byte>();

		private List<PokerTable> m_Tables;
		private List<PokerPlayer> m_Players;

		private PokerTournamentConfiguration()
		{
			m_Currency = PokerCurrency.Play;
		}
		public PokerTournamentConfiguration(string tournamentName, int minEntries, int maxEntries, params BettingStructureEntry[] bettingStructure) : this()
		{
			m_MinimumEntries = minEntries;
			m_MaximumEntries = maxEntries;
			m_BettingStructure = bettingStructure;
			m_TournamentName = tournamentName;

			//CreateBettingStructure(40, 10, new ulong[] { 1, 5, 25, 100, 500, 1000, 5000, 25000, 100000, 500000, 1000000, 5000000 });
		}

		public ulong TotalAmountInPlay { get { return (ulong)EntryCount * m_StartingChips + (ulong)m_TotalAddOnCount * m_AddOnChips + (ulong)m_TotalRebuyCount * m_RebuyChips; } }
		public ulong AverageAmount { get { return m_Finished ? TotalAmountInPlay : TotalAmountInPlay / (ulong)PlayersLeft; } }
		public int PlayersLeft
		{
			get
			{
				if(m_Players != null)
					lock (m_Players)
						return m_Players.Count;
				return EntryCount;
			}
		}

		private void CreateBettingStructure(int levels, ulong startSmallBlind, ulong[] chipsValue)
		{
			ulong smallBlind = startSmallBlind;
			m_BettingStructure = new BettingStructureEntry[levels];
			for (int c = 0; c < levels; c++)
			{
				BettingStructureEntry entry = new BettingStructureEntry();
				entry.SmallBlind = smallBlind;
				entry.BigBlind = smallBlind * 2;
				entry.Duration = 15 * 60 * 1000;
				entry.Ante = RoundToChipsValue(smallBlind / 4, chipsValue);
				entry.TimeBank = 0;
				entry.Level = c;
				m_BettingStructure[c] = entry;

				smallBlind = RoundToChipsValue((ulong)(smallBlind * 1.33), chipsValue);
			}

			SmoothBettingStructure(chipsValue);
		}

		private void SmoothBettingStructure(ulong[] chipsValue)
		{
			ulong previousJump = 0;
			for (int c = 1; c < m_BettingStructure.Length; c++)
			{
				ulong jump = m_BettingStructure[c].SmallBlind - m_BettingStructure[c - 1].SmallBlind;
				if (jump < previousJump)
				{
					m_BettingStructure[c - 1].SmallBlind = RoundToChipsValue(m_BettingStructure[c - 2].SmallBlind + ((jump + previousJump) / 2), chipsValue);
					m_BettingStructure[c - 1].BigBlind = m_BettingStructure[c - 1].SmallBlind * 2;
					m_BettingStructure[c - 1].Ante = RoundToChipsValue(m_BettingStructure[c - 1].SmallBlind / 4, chipsValue);
				}
				else
					previousJump = jump;
			}
		}

		private ulong RoundToChipsValue(ulong value, ulong[] chipsValue)
		{
			if (value < chipsValue[0])
				return 0;
			int index = 0;
			ulong count;
			ulong remainder;
			for (; index < chipsValue.Length; index++)
				if (value < chipsValue[index])
				{
					double diff = value * 1.0 / chipsValue[index];
					if (diff > 0.8)
					{
						return chipsValue[index];
					}
					else
					{
						count = value / chipsValue[index - 1];
						remainder = value % chipsValue[index - 1];
						if (remainder > chipsValue[index - 1] / 2)
						{
							remainder = 0;
							count++;
						}
						if (index > 1 && remainder > chipsValue[index - 2])
						{
							ulong count2 = remainder / chipsValue[index - 2];
							return count * chipsValue[index - 1] + count2 * chipsValue[index - 2];
						}
						if (index > 2 && remainder > chipsValue[index - 3])
						{
							ulong count2 = remainder / chipsValue[index - 3];
							return count * chipsValue[index - 1] + count2 * chipsValue[index - 3];
						}
						return count * chipsValue[index - 1];
					}
				}

			count = value / chipsValue[index - 1];
			remainder = value % chipsValue[index - 1];
			if (remainder > chipsValue[index - 1] / 2)
			{
				remainder = 0;
				count++;
			}
			if (index > 1 && remainder > chipsValue[index - 2])
			{
				ulong count2 = remainder / chipsValue[index - 2];
				return count * chipsValue[index - 1] + count2 * chipsValue[index - 2];
			}
			if (index > 2 && remainder > chipsValue[index - 3])
			{
				ulong count2 = remainder / chipsValue[index - 3];
				return count * chipsValue[index - 1] + count2 * chipsValue[index - 3];
			}
			return count * chipsValue[index - 1];
		}

		public PokerTableSeat GetTableSeat(PokerPlayer player)
		{
			lock(m_Tables)
			{
				foreach(PokerTable table in m_Tables)
				{
					PokerTableSeat seat = table[player.Login];
					if (seat != null)
						return seat;
				}
			}
			return null;
		}

		public string TournamentName { get { return m_TournamentName; } }
		public ulong StartingChips { get { return m_StartingChips; } }
		public int Level { get { return m_Level; } }
		public ulong EntryPrice { get { return m_EntryPrice; } }
		public ulong RebuyPrice { get { return m_RebuyPrice; } }
		public ulong AddOnPrice { get { return m_AddOnPrice; } }
		public ulong EntryRake { get { return m_EntryRake; } }
		public int MaximumEntries { get { return m_MaximumEntries; } }
		public int MinimumEntries { get { return m_MinimumEntries; } }
		public DateTime StartTime { get { return m_StartTime; } }

		public bool IsRegistered(PokerPlayer player)
		{
			if (player == null)
				return false;
			lock (m_Entrants)
			{
				return m_Entrants.Contains(player);
			}
		}

		public override bool IsTournament { get { return true; } }
		public override bool IsCashGame { get { return false; } }

		public List<PokerTable> Tables { get { return m_Tables; } }

		protected virtual PokerDealer NewDealer()
		{
			return new TexasHoldemDealer();
		}

		public PokerCurrency EntryCurrency { get { return m_EntryCurrency; } }

		public override ulong Ante { get { return m_BettingStructure[m_Level].Ante; } }
		public override ulong SmallBlind { get { return m_BettingStructure[m_Level].SmallBlind; } }
		public override ulong BigBlind { get { return m_BettingStructure[m_Level].BigBlind; } }

		public int GetLevel(PokerDealer dealer) { return GetLevel(dealer.HandDateTime); }
		public int GetLevel(DateTime time)
		{
			TimeSpan duration = time.Subtract(m_StartTime);
			int levelIndex = 0;
			while (duration.TotalMilliseconds > 0 && levelIndex < m_BettingStructure.Length)
			{
				duration = duration.Subtract(new TimeSpan(0, 0, 0, 0, m_BettingStructure[levelIndex].Duration));
				levelIndex++;
			}
			return Math.Max(0, levelIndex - 1);
		}
		public PayoutEntry GetPayout(int position)
		{
			if(position > m_Payout[m_Payout.Length - 1].LastPosition)
				return null;
			for (int c = 0; c < m_Payout.Length; c++)
			{
				if (m_Payout[c].LastPosition <= position)
					return m_Payout[c];
			}
			return null;
		}

		public override ulong GetAnte(PokerDealer dealer) { return m_BettingStructure[GetLevel(dealer)].Ante; }
		public override ulong GetSmallBlind(PokerDealer dealer) { return m_BettingStructure[GetLevel(dealer)].SmallBlind; }
		public override ulong GetBigBlind(PokerDealer dealer) { return m_BettingStructure[GetLevel(dealer)].BigBlind; }
		public override PokerLimit Limit { get { return m_Limit; } }
		public override PokerType Type { get { return m_Type; } }

		public int EntryCount { get { return m_Entrants == null ? 0 : m_Entrants.Count; } }

		public bool Enter(PokerPlayer player)
		{
			if (player == null)
				return false;
			lock (m_Entrants)
			{
				if (m_Finished)
					return false;
				bool lateReg = LateRegistrationAvailable;
				if (m_Started && !lateReg)
					return false;
				if (m_Entrants.Contains(player))
					return false;
				if (m_Entrants.Count >= m_MaximumEntries)
					return false;
				ulong amount = (m_EntryPrice + m_EntryRake);
				if (player.BankRoll >= amount)
				{
					player.BankRoll -= amount;
					m_Entrants.Add(player);
					if (lateReg)
						AddLatePlayer(player);
					return true;
				}
				else
					return false;
			}
		}
		public uint Exit(PokerPlayer player)
		{
			lock (m_Players)
			{
				lock (m_Finisheds)
				{
					int index = m_Players.IndexOf(player);
					if (index < 0)
						return 0;
					m_Players.RemoveAt(index);
					m_Finisheds.Add(player);
					int position = m_Players.Count;
					PayoutEntry payout = GetPayout(position);
					if (payout != null)
					{
						m_PayoutLocked = true;
						player.BankRoll += payout.Amount;
					}
					player.SendTournamentResult(this, position, payout);
					if (position == 1)
					{
						PokerPlayer winner = m_Players[0];
						m_Finisheds.Add(winner);
						payout = GetPayout(0);
						if (payout != null)
						{
							m_PayoutLocked = true;
							winner.BankRoll += payout.Amount;
						}
						winner.SendTournamentResult(this, position - 1, payout);
						m_Finished = true;
					}
					return (uint)position;
				}
			}
		}

		public bool IsMultiTable { get { return m_MaximumEntries > NumberOfSeats; } }
		public bool IsSingleTable { get { return m_MaximumEntries <= NumberOfSeats; } }
		public bool IsFinalTable { get { return m_Started && !m_Finished && m_Tables.Count == 1; } }

		public virtual PayoutEntry[] BuildPayout()
		{
			int entrants = 0;
			lock (m_Entrants)
			{
				entrants = m_Entrants.Count;
			}
			if (entrants > 25)
				throw new NotImplementedException();
			ulong prizePool = m_EntryPrice * (ulong)entrants + m_RebuyPrice * (ulong)m_TotalRebuyCount;
			double[] percentages = null;
			if (entrants <= 5)
				percentages = new double[] { 1 };
			else if (entrants <= 8)
				percentages = new double[] { 0.7, 0.3 };
			else if(entrants <= 15)
				percentages = new double[] { 0.5, 0.3, 0.2 };
			else if(entrants <= 25)
				percentages = new double[] { 0.5, 0.25, 0.15, 0.1 };
			else
				percentages = new double[] { 0.4, 0.25, 0.2, 0.1, 0.05 };

			PayoutEntry[] payout = new PayoutEntry[percentages.Length];
			ulong usedPrizePool = 0;
			for (int c = 0; c < payout.Length; c++)
			{
				PayoutEntry entry = new PayoutEntry();
				entry.Amount = (ulong)(prizePool * percentages[c]);
				usedPrizePool += entry.Amount;
				entry.FirstPosition = entry.LastPosition = c;
				payout[c] = entry;
			}
			if (usedPrizePool != prizePool)
			{
				if (usedPrizePool < prizePool)
					payout[payout.Length - 1].Amount += (prizePool - usedPrizePool);
				else
					payout[payout.Length - 1].Amount += (usedPrizePool - prizePool);
			}
			return payout;
		}

		public void EquilibrateTables()
		{
			lock (m_Players)
			{
				lock (m_Tables)
				{
					List<PokerTable> tables = new List<PokerTable>(m_Tables);
					int count = tables.Count;
					if (count > 1)
					{
						int neededTable = m_Players.Count / NumberOfSeats;
						int remainder = (m_Players.Count % NumberOfSeats);
						if (remainder != 0)
							neededTable++;
						Random rand = new Random();
						if (neededTable < count)
						{
							// we have more table than we should have.
							List<PokerTable> tableWithMissingPeople = new List<PokerTable>();
							List<PokerTable> tableFull = new List<PokerTable>();
							PokerTable tableToRemove = null;
							int min = 9;
							foreach (PokerTable table in tables)
							{
								int noPl = table.NumberOfPlayers;
								if (noPl < NumberOfSeats)
								{
									if (noPl < min)
									{
										min = noPl;
										tableToRemove = table;
									}
									tableWithMissingPeople.Add(table);
								}
								else
								{
									tableFull.Add(table);
								}
							}
							// stop the shortest table and wait the dealer to end the hand.
							tableToRemove.Stop();
							// we are already in dealer's thread so he is waiting now ...
							if (Thread.CurrentThread != tableToRemove.Dealer.Thread)
								tableToRemove.Dealer.Join();
							List<KeyValuePair<PokerPlayer, ulong>> playersToDistribute = new List<KeyValuePair<PokerPlayer, ulong>>();
							List<PokerTableSeat> seats = new List<PokerTableSeat>(tableToRemove.OccupiedSeats);
							foreach (PokerTableSeat seat in seats)
							{
								playersToDistribute.Add(new KeyValuePair<PokerPlayer, ulong>(seat.Player, seat.Stack));
								tableToRemove.Leave(seat.Index);
							}
							m_Tables.Remove(tableToRemove);
							tables.Remove(tableToRemove);
							tableWithMissingPeople.Remove(tableToRemove);
							// distribute the player in on of the table with missing player
							while (playersToDistribute.Count > 0)
							{
								if (tableWithMissingPeople.Count == 0)
									throw new Exception("should never happen or we did something wrong.");
								int tableIndex = rand.Next(tableWithMissingPeople.Count);
								int playerIndex = rand.Next(playersToDistribute.Count);
								PokerTable table = tableWithMissingPeople[tableIndex];
								KeyValuePair<PokerPlayer, ulong> playerPair = playersToDistribute[playerIndex];
								playersToDistribute.RemoveAt(playerIndex);
								int seatIndex = table.FirstEmptySeat;
								table.Sit(seatIndex, playerPair.Key, playerPair.Value);
								if (table.NumberOfPlayers == NumberOfSeats)
								{
									// the table is now full.
									tableWithMissingPeople.Remove(table);
									tableFull.Add(table);
								}
							}
						}
						int minPlayerPerTable = m_Players.Count / tables.Count;
						remainder = m_Players.Count % tables.Count;
						List<PokerTable> tableWithLess = new List<PokerTable>();
						List<PokerTable> tableWithMin = new List<PokerTable>();
						List<PokerTable> tableWithMax = new List<PokerTable>();
						List<PokerTable> tableWithMore = new List<PokerTable>();
						foreach (PokerTable table in tables)
						{
							int noPl = table.NumberOfPlayers;
							if (noPl < minPlayerPerTable)
								tableWithLess.Add(table);
							else if (noPl == minPlayerPerTable)
								tableWithMin.Add(table);
							else if (noPl == minPlayerPerTable + 1)
								tableWithMax.Add(table);
							else
								tableWithMore.Add(table);
						}
						while (tableWithLess.Count > 0)
						{
							int seatIndex = tableWithLess[0].FirstEmptySeat;
							if (tableWithMore.Count > 0)
							{
								int tableIndex = -1;
								int otherSeatIndex = -1;
								// this is a dangerous part of code ...
								do
								{
									Thread.Sleep(100);
									tableIndex = rand.Next(tableWithMore.Count);
									otherSeatIndex = tableWithMore[tableIndex].Dealer.FirstIdlePlayerIndex;
								} while (otherSeatIndex == -1);
								PokerPlayer player = tableWithMore[tableIndex][otherSeatIndex].Player;
								ulong amount = tableWithMore[tableIndex][otherSeatIndex].Stack;
								tableWithMore[tableIndex].Leave(otherSeatIndex);
								tableWithLess[0].Sit(seatIndex, player, amount);

								if (tableWithMore[tableIndex].NumberOfPlayers == minPlayerPerTable + 1)
								{
									tableWithMax.Add(tableWithMore[tableIndex]);
									tableWithMore.RemoveAt(tableIndex);
								}
								if (tableWithLess[0].NumberOfPlayers == minPlayerPerTable)
								{
									tableWithMin.Add(tableWithLess[0]);
									tableWithLess.RemoveAt(0);
								}
							}
							else if (tableWithMax.Count > 0)
							{
								int tableIndex = -1;
								int otherSeatIndex = -1;
								// this is a dangerous part of code ...
								do
								{
									Thread.Sleep(100);
									tableIndex = rand.Next(tableWithMax.Count);
									otherSeatIndex = tableWithMax[tableIndex].Dealer.FirstIdlePlayerIndex;
								} while (otherSeatIndex == -1);
								PokerPlayer player = tableWithMax[tableIndex][otherSeatIndex].Player;
								ulong amount = tableWithMax[tableIndex][otherSeatIndex].Stack;
								tableWithMax[tableIndex].Leave(otherSeatIndex);
								tableWithLess[0].Sit(seatIndex, player, amount);

								tableWithMin.Add(tableWithMax[tableIndex]);
								tableWithMax.RemoveAt(tableIndex);
								if (tableWithLess[0].NumberOfPlayers == minPlayerPerTable)
								{
									tableWithMin.Add(tableWithLess[0]);
									tableWithLess.RemoveAt(0);
								}
							}
							else
								tableWithLess.RemoveAt(0);
						}
					}
				}
			}
		}

		public void Run()
		{
			if (m_Started)
				throw new Exception("Tournament already started.");
			int tableCount = 1;
			Random rand = new Random();
			List<PokerPlayer> players = null;
			lock (m_Entrants)
			{
				m_Started = true;
				tableCount = m_Entrants.Count / NumberOfSeats;
				if ((m_Entrants.Count % NumberOfSeats) != 0)
					tableCount++;
				players = new List<PokerPlayer>(m_Entrants);
				m_Payout = BuildPayout();
			}
			List<PokerTable> tables = new List<PokerTable>();
			for (int c = 0; c < tableCount; c++)
			{
				PokerTable table = new PokerTable(TournamentName + " - Table #" + (c + 1), this);
				table.Dealer = NewDealer();
				tables.Add(table);
			}
			int tableIndex = 0;
			int seatIndex = 0;
			m_Players = new List<PokerPlayer>(players);
			while (players.Count > 0)
			{
				PokerTable table = tables[tableIndex];
				int playerIndex = rand.Next(players.Count);
				PokerPlayer player = players[playerIndex];
				players.RemoveAt(playerIndex);
				table.Sit(seatIndex, player, m_StartingChips);
				tableIndex++;
				if (tableIndex >= tables.Count)
				{
					tableIndex = 0;
					seatIndex++;
				}
			}
			m_Tables = tables;
			List<PokerTable> allTables = new List<PokerTable>(tables);

			Thread levelThread = new Thread(delegate()
			{
				m_StartTime = DateTime.Now;
				for (int c = 0; c < m_BettingStructure.Length && !m_Finished; c++)
				{
					if (c != 0)
					{
						lock (m_Tables)
						{
							foreach (PokerTable table in m_Tables)
							{
								table.BroadCastBlindGoesUp(m_BettingStructure[m_Level].SmallBlind, m_BettingStructure[m_Level].BigBlind, m_BettingStructure[m_Level].Ante);
							}
						}
					}
					Thread.Sleep(m_BettingStructure[c].Duration);
					m_Level = c + 1;
				}
			});

			levelThread.Start();
			foreach (PokerTable table in allTables)
				table.Dealer.Start();

			foreach (PokerTable table in allTables)
				table.Dealer.Thread.Join();
			if (levelThread.ThreadState != ThreadState.Stopped)
				levelThread.Abort();

			m_Finished = true;
			m_Thread = null;
		}

		private Thread m_Thread = null;
		public void Start(DateTime startDate)
		{
			m_StartTime = startDate;
			if (m_Thread != null)
				throw new Exception("already started thread.");

			m_Thread = new Thread(delegate()
			{
				TimeSpan duration = startDate.Subtract(DateTime.Now);
				if (duration.TotalMilliseconds >= 0)
					Thread.Sleep(duration);
				Run();
			});
			m_Thread.Start();
		}

		public override bool Finished { get { return m_Started && m_Finished; } }

		public bool Unregister(PokerPlayer player)
		{
			if (m_Started)
				return false;
			lock (m_Entrants)
			{
				if (!m_Entrants.Contains(player))
					return false;
				m_Entrants.Remove(player);
				ulong amount = m_EntryPrice + m_EntryRake;
				player.BankRoll += amount;
				return true;
			}
		}

		public List<KeyValuePair<string, ulong>> PlayersAmount
		{
			get
			{
				List<KeyValuePair<string, ulong>> results = new List<KeyValuePair<string, ulong>>();
				if(m_Started)
				{
					lock (m_Players)
					{
						lock (m_Finisheds)
						{
							for (int c = 0; c < m_Finisheds.Count; c++)
								results.Add(new KeyValuePair<string, ulong>(m_Finisheds[c].Login, 0));
							for (int c = 0; c < m_Players.Count; c++)
								results.Add(new KeyValuePair<string, ulong>(m_Players[c].Login, GetTableSeat(m_Players[c]).Stack));
						}
					}
				}
				else
				{
					foreach(PokerPlayer entrant in m_Entrants)
						results.Add(new KeyValuePair<string, ulong>(entrant.Login, m_StartingChips));
				}
				return results;
			}
		}

		public BettingStructureEntry[] BettingStructure { get { return m_BettingStructure; } }

		public PayoutEntry[] PayoutStructure
		{
			get
			{
				if (m_Payout != null)
					return m_Payout;
				if (m_Entrants.Count == 0)
					return null;
				return BuildPayout();
			}
		}


		public bool LateRegistrationAvailable { get { return !m_PayoutLocked && DateTime.Now.CompareTo(m_StartTime) > 0 && DateTime.Now.CompareTo(m_StartTime.AddMinutes(m_LateRegigstrationDuration)) < 0; } }


		private void AddLatePlayer(PokerPlayer player)
		{
			lock (m_Players)
			{
				lock (m_Tables)
				{
					m_Players.Add(player);
					List<PokerTable> tables = new List<PokerTable>(m_Tables);
					int count = tables.Count;

					int neededTable = m_Players.Count / NumberOfSeats;
					int remainder = (m_Players.Count % NumberOfSeats);
					if (remainder != 0)
						neededTable++;
					Random rand = new Random();

					if (neededTable > count)
					{
						PokerTable table = new PokerTable(TournamentName + " - Table #" + (count + 1), this);
						table.Dealer = NewDealer();
						m_Tables.Add(table);
						table.Dealer.Start();

						table.Sit(0, player, m_StartingChips);
						EquilibrateTables();
					}
					else
					{
						if (tables.Count == 1)
						{
							int seatIndex = tables[0].FirstEmptySeat;
							tables[0].Sit(seatIndex, player, m_StartingChips);
						}
						else
						{
							int minPlayerPerTable = m_Players.Count / tables.Count;
							remainder = m_Players.Count % tables.Count;
							List<PokerTable> tableWithLess = new List<PokerTable>();
							List<PokerTable> tableWithMin = new List<PokerTable>();
							foreach (PokerTable table in tables)
							{
								int noPl = table.NumberOfPlayers;
								if (noPl < minPlayerPerTable)
								{
									int seatIndex = table.FirstEmptySeat;
									table.Sit(seatIndex, player, m_StartingChips);
									return;
								}
								else if (noPl == minPlayerPerTable)
									tableWithMin.Add(table);
							}
							if (tableWithMin.Count > 0)
							{
								int seatIndex = tableWithMin[0].FirstEmptySeat;
								tableWithMin[0].Sit(seatIndex, player, m_StartingChips);
							}
							else
								throw new NotImplementedException();
						}
					}

					m_Payout = BuildPayout();
				}
			}
		}

		public bool CanRebuy(PokerPlayer pokerPlayer)
		{
			if (!m_Started)
				return false;
			if (m_PayoutLocked)
				return false;
			DateTime limit = m_StartTime.AddMinutes(m_RebuyDuration);
			if (limit.CompareTo(DateTime.Now) < 0)
				return false;
			if (m_RebuyAllowed == 0)
				return false;
			byte rebuyUsed = 0;
			if (!m_RebuyCount.TryGetValue(pokerPlayer, out rebuyUsed))
				rebuyUsed = 0;
			if (m_RebuyAllowed <= rebuyUsed)
				return false;
			if (pokerPlayer.BankRoll < m_RebuyPrice)
				return false;
			return true;
		}

		public ulong RebuyChips { get { return m_RebuyChips; } }

		public byte Rebuy(PokerPlayer pokerPlayer, byte rebuyCount)
		{
			byte rebuyUsed = 0;
			if (!m_RebuyCount.TryGetValue(pokerPlayer, out rebuyUsed))
			{
				m_RebuyCount.Add(pokerPlayer, 0);
				rebuyUsed = 0;
			}
			if (m_RebuyAllowed < rebuyUsed + rebuyCount)
				rebuyCount = (byte)((int)m_RebuyAllowed - (int)rebuyUsed);
			m_RebuyCount[pokerPlayer] += rebuyCount;
			m_TotalRebuyCount+=rebuyCount;
			m_Payout = BuildPayout();
			return rebuyCount;
		}
	}
}
